/**************************************************************************//**
* @file hrm_app.c
* @brief BLE HRM/SpO2 demo using Maxm86161 sensor
* @version 1.0.0
*******************************************************************************
* # License
* <b>Copyright 2020 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* SPDX-License-Identifier: Zlib
*
* The licensor of this software is Silicon Laboratories Inc.
*
* This software is provided \'as-is\', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
*    claim that you wrote the original software. If you use this software
*    in a product, an acknowledgment in the product documentation would be
*    appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
*    misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
*******************************************************************************
* # Experimental Quality
* This code has not been formally tested and is provided as-is. It is not
* suitable for production environments. In addition, this code will not be
* maintained and there may be no bug maintenance planned for these resources.
* Silicon Labs may update projects from time to time.
******************************************************************************/
#include "hrm_helper.h"
#include "maxm86161_hrm_config.h"
#include "maxm86161_hrm_spo2.h"
#include "em_gpio.h"
#include "em_cmu.h"
#include "em_emu.h"
#include "sl_i2cspm.h"
#include "sl_bt_api.h"
#include "sl_simple_button_instances.h"
#include "sl_sleeptimer.h"
#include "hrm_app.h"
#include "oled_display.h"
#include "glib_font.h"
#include "sl_sleeptimer.h"
#include "sl_i2cspm_instances.h"
#include "app_log.h"
#include "glib.h"
#include "gpiointerrupt.h"
#include <stdio.h>

/**************************************************************************//**
 * Local defines
 *****************************************************************************/

/**************************************************************************//**
 * Global variables
 *****************************************************************************/

/** Sensor handle for the maxm86161 hrm algorithm */
maxm_hrm_handle_t *hrmHandle;

/** Data Storage memory bock required by the maxm86161 hrm algorithm */
mamx86161_hrm_data_storage_t hrm_data_storage;
maxm86161_spo2_data_storage_t spo2_data;
maxm86161_data_storage_t data_storage;
mamx86161_hrm_data_t hrm_data;

/**************************************************************************//**
 * Local variables
 *****************************************************************************/

/** Heart Rate result from the si117xhrm algorithm */
static int16_t heart_rate;

/** SpO2 result from the si117xhrm algorithm */
static int16_t spo2;
static int32_t hrm_status = 0;
static bool update_display = false;
static bool hrm_contac_status = false;
static sl_sleeptimer_timer_handle_t oled_timer;

/** Flag used to indicate HRM/SpO2 state */
static volatile hrm_spo2_state_t hrm_spo2_state = HRM_STATE_IDLE;

/*Context use for oled glib*/
static glib_context_t glib_context;

/* This array is generated by GIMP application. A image with bitmap format can
 * use this tool to convert to code.
 * To draw an image or custom bitmaps on the oled, the glib_draw_xbitmap()
 * function can be used.
 * */
static const unsigned char silicon_labs_logo_64x23[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
  0x00, 0x00, 0x00, 0x80, 0xff, 0x1f, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xf0,
  0xff, 0x7f, 0x00, 0x3e, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x7f, 0x00, 0x7f,
  0x00, 0x00, 0x00, 0xff, 0xff, 0x3f, 0x80, 0x7f, 0x00, 0x06, 0x80, 0xff,
  0xff, 0xff, 0xe0, 0xff, 0x80, 0x03, 0xc0, 0xff, 0x01, 0xf8, 0xff, 0x7f,
  0xe0, 0x01, 0xc0, 0x03, 0x00, 0x00, 0xfe, 0x7f, 0xf0, 0x01, 0x00, 0x01,
  0x00, 0x00, 0xf8, 0x3f, 0xf8, 0x03, 0x00, 0xf0, 0x07, 0x00, 0xe0, 0x3f,
  0xfc, 0x03, 0x00, 0x00, 0x20, 0x00, 0xe0, 0x1f, 0xfe, 0x0f, 0x00, 0x00,
  0xc0, 0x00, 0xe0, 0x07, 0xfe, 0xff, 0x00, 0x00, 0xfc, 0x00, 0xe0, 0x03,
  0xff, 0xc3, 0xff, 0xff, 0xff, 0x00, 0xf0, 0x00, 0x7e, 0x00, 0xfe, 0xff,
  0x7f, 0x00, 0x38, 0x00, 0x3e, 0x00, 0xff, 0xff, 0x3f, 0x00, 0x0c, 0x00,
  0x1c, 0x80, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x18, 0x00, 0xff, 0xff,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
};

static unsigned char hrm_background[] = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x01, 0x8f, 0x07, 0x00,
  0x00, 0x00, 0x00, 0x84, 0x81, 0xdf, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x84,
  0xc1, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xbc, 0xe1, 0xff, 0x3f, 0x00,
  0x00, 0x00, 0x00, 0xa4, 0xe1, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xa4,
  0xe1, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xbc, 0xe1, 0xff, 0x3f, 0x00,
  0x00, 0x00, 0x00, 0x80, 0xe1, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xbc,
  0xc1, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x81, 0xff, 0x0f, 0x00,
  0x00, 0x00, 0x00, 0xa4, 0x01, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0xbc,
  0x01, 0xfe, 0x03, 0x00, 0x00, 0x00, 0x00, 0x84, 0x01, 0xfc, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x84, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
  0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0x01, 0x20, 0x00, 0x00,
  0x00, 0x00, 0x00, 0xaa, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0xaa, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
  0x81, 0xef, 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0x81, 0x28, 0x02, 0x00,
  0x00, 0x00, 0x00, 0x80, 0x81, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80,
  0x81, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80, 0x81, 0xef, 0x03, 0x00,
  0x00, 0x00, 0x00, 0x80, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
  0x01, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x81, 0x28, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x80, 0x81, 0x2f, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x80,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x90, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0xe0, 0x88, 0x81, 0xef, 0x03, 0x00, 0x00, 0x00, 0x00, 0x84,
  0x81, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x82, 0x81, 0x08, 0x02, 0x00,
  0x00, 0x00, 0x00, 0xb9, 0x81, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x80, 0xa8,
  0x81, 0x28, 0x00, 0x00, 0x00, 0x00, 0x40, 0xb8, 0x81, 0x28, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x80, 0x81, 0x28, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80,
  0x81, 0xef, 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

/**************************************************************************//**
 * Local prototypes
 *****************************************************************************/
static void hrm_gpio_setup(void);
static void oled_app_init(void);
static void app_oled_timer_callback(sl_sleeptimer_timer_handle_t *timer,
                                    void *data);
static void app_hrm_dislay_background(void);
static void app_hrm_gpio_callback(uint8_t pin);

/**************************************************************************//**
 * @brief Initialize the HRM application.
 *****************************************************************************/
void hrm_init_app(void)
{
  int32_t err;

  /* Initialize the oled. */
  oled_app_init();

  /* Initialize other peripherals and drivers */
  hrm_gpio_setup();
  // Initialize hrm
  data_storage.spo2 = &spo2_data;
  data_storage.hrm = &hrm_data_storage;
  err = maxm86161_hrm_initialize(&data_storage, &hrmHandle);

  maxm86161_hrm_configure(hrmHandle, NULL, true);

  // if a max86161 is not present on the I2C bus, then shows an error on OLED
  if (err == MAXM86161_HRM_ERROR_INVALID_PART_ID) {
    app_log("[ERROR]: Heart rate initialized failed.\n");
    glib_set_font(&glib_context, NULL);
    glib_draw_string(&glib_context, "Error:", 0, 2);
    glib_draw_string(&glib_context, "cannot", 8, 12);
    glib_draw_string(&glib_context, "detect", 8, 22);
    glib_draw_string(&glib_context, "MAX86161", 8, 32);
    glib_update_display();
  } else {
    app_log("[HR]: Heart rate initialized successfully.\n");
    glib_draw_xbitmap(&glib_context,
                      0, 10, silicon_labs_logo_64x23,
                      64, 23, GLIB_WHITE);
  }

  sl_sleeptimer_delay_millisecond(3000);
  glib_enable_display(false);

  sl_sleeptimer_start_periodic_timer_ms(&oled_timer,
                                        500,
                                        app_oled_timer_callback,
                                        NULL,
                                        0,
                                        0);
}

/**************************************************************************//**
 * @brief Setup GPIO, enable sensor isolation switch
 *****************************************************************************/
static void hrm_gpio_setup(void)
{
  /* Enable GPIO clock */
  CMU_ClockEnable(cmuClock_GPIO, true);
  GPIO_PinModeSet(MAXM86161_EN_GPIO_PORT,
                  MAXM86161_EN_GPIO_PIN,
                  gpioModePushPull,
                  1);

  /*Set up GPIO interrupt for handler data from sensor*/
  GPIO_PinModeSet(MAX86161_GPIO_INT_PORT,
                  MAX86161_GPIO_INT_PIN,
                  gpioModeInputPull,
                  1);
  GPIOINT_Init();
  GPIOINT_CallbackRegister(MAX86161_GPIO_INT_PIN, app_hrm_gpio_callback);
  GPIO_ExtIntConfig(MAX86161_GPIO_INT_PORT,
                    MAX86161_GPIO_INT_PIN,
                    MAX86161_GPIO_INT_PIN,
                    true,
                    true,
                    true);

  // need delay to wait the Maxim ready before we can read and write to register
  sl_sleeptimer_delay_millisecond(5);
}

/**************************************************************************//**
 * Simple Button
 * Button state changed callback
 * @param[in] handle
 *    Button event handle
 *****************************************************************************/
void sl_button_on_change(const sl_button_t *handle)
{
  // Button pressed.
  if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_PRESSED) {
    if (&sl_button_btn0 == handle) {
      sl_bt_external_signal(BTN0_IRQ_EVENT);
    }
  }
}

/**************************************************************************//**
 * @brief Process HRM IRQ events.
 *****************************************************************************/
void hrm_process_event(uint32_t event_flags)
{
  if (event_flags & MAXM86161_IRQ_EVENT) {
    maxm86161_hrm_helper_process_irq();
  }

  if (event_flags & BTN0_IRQ_EVENT) {
    if (hrm_spo2_state == HRM_STATE_IDLE) {
      /* Fill lcd with background color */
      glib_enable_display(true);
      app_hrm_dislay_background();
      hrm_spo2_state = HRM_STATE_ACQUIRING;
      maxm86161_hrm_run(hrmHandle);
    } else {
      hrm_spo2_state = HRM_STATE_IDLE;
      maxm86161_hrm_pause();
      glib_enable_display(false);
    }
  }
}

/**************************************************************************//**
 * @brief HRM process main loop.
 *****************************************************************************/
void hrm_loop(void)
{
  int16_t num_samples_processed;
  int32_t err;

  err = maxm86161_hrm_process(hrmHandle, &heart_rate, &spo2, 1,
                              &num_samples_processed, &hrm_status, &hrm_data);

  switch (hrm_spo2_state) {
    case HRM_STATE_IDLE:
      update_display = false;
      break;
    case HRM_STATE_ACQUIRING:
    case HRM_STATE_ACTIVE:
      if ((err == MAXM86161_HRM_SUCCESS)
          && (hrm_status & MAXM86161_HRM_STATUS_FRAME_PROCESSED)) {
        hrm_status &= ~MAXM86161_HRM_STATUS_FRAME_PROCESSED;

#if (UART_DEBUG & HRM_LEVEL)
        hrm_helper_output_debug_message(heart_rate, spo2);
#endif
        update_display = true;
        hrm_spo2_state = HRM_STATE_ACTIVE;
      }
#ifdef PROXIMITY
      else if ((hrm_status & MAXM86161_HRM_STATUS_FINGER_OFF)
               || (hrm_status & MAXM86161_HRM_STATUS_SPO2_FINGER_OFF)
               || (hrm_status & MAXM86161_HRM_STATUS_ZERO_CROSSING_INVALID)
               || (maxm86161_get_prox_mode())) {
#else
      else if ((hrm_status & MAXM86161_HRM_STATUS_FINGER_OFF)
               || (hrm_status & MAXM86161_HRM_STATUS_SPO2_FINGER_OFF)
               || (hrm_status & MAXM86161_HRM_STATUS_ZERO_CROSSING_INVALID)) {
#endif
        heart_rate = 0;
        spo2 = 0;
        update_display = true;
        hrm_spo2_state = HRM_STATE_ACQUIRING;
      }
      break;

    default:
      break;
  }
}

/**************************************************************************//**
 * @brief This function returns the current heart rate.
 *****************************************************************************/
int16_t hrm_get_heart_rate(void)
{
  return heart_rate;
}

/**************************************************************************//**
 * @brief This function returns the current finger contact status.
 *****************************************************************************/
bool hrm_get_status(void)
{
  hrm_contac_status = (hrm_spo2_state == HRM_STATE_ACTIVE);

  return hrm_contac_status;
}

/**************************************************************************//**
 * @brief This function returns the SpO2.
 *****************************************************************************/
int16_t hrm_get_spo2(void)
{
  return spo2;
}

/**************************************************************************//**
 * Initialize OLED display.
 *****************************************************************************/
static void oled_app_init(void)
{
  if (ssd1306_init(sl_i2cspm_qwiic) == SL_STATUS_OK) {
    app_log("[OLED]: SparkFun Oled initialized successfully.\r\n");
  } else {
    app_log("[OLED]: SparkFun Oled initialization failed!\r\n");
  }

  glib_init(&glib_context);
  glib_context.bg_color = GLIB_BLACK;
  glib_context.text_color = GLIB_WHITE;

  /* Fill lcd with background color */
  glib_clear(&glib_context);
}

/**************************************************************************//**
 * @brief Update the HRM/SpO2 Demo LCD Display
 *****************************************************************************/
void hrm_update_display(void)
{
  glib_status_t status;
  static char temp[11];

  if (update_display) {
    update_display = false;
    glib_clear(&glib_context);
    glib_draw_xbitmap(&glib_context, 0, 0,
                      hrm_background, 64, 48, GLIB_WHITE);
    glib_set_font(&glib_context, &glib_font_free_sans_9pt7b);
    snprintf(temp, sizeof(temp), "%d", heart_rate);

    if (heart_rate >= 100) {
      glib_draw_string(&glib_context, temp, 23, 18);
    } else {
      glib_draw_string(&glib_context, temp, 30, 18);
    }
    snprintf(temp, sizeof(temp), "%d", spo2);
    glib_draw_string(&glib_context, temp, 30, 43);

    status = glib_update_display();
    if (GLIB_OK != status) {
      return;
    }
  }
}

/**************************************************************************//**
 * @brief   Display background on oled screen.
 *
 *****************************************************************************/
static void app_hrm_dislay_background(void)
{
  glib_status_t status;

  glib_clear(&glib_context);
  glib_draw_xbitmap(&glib_context, 0, 0,
                    hrm_background, 64, 48, GLIB_WHITE);

  status = glib_update_display();
  if (GLIB_OK != status) {
    return;
  }
}

/**************************************************************************//**
 * @brief   A callback to handle sleep timer.
 *
 * @param[in] pin  timer handler.
 * @param[in] data.
 *****************************************************************************/
static void app_oled_timer_callback(sl_sleeptimer_timer_handle_t *timer,
                                    void *data)
{
  (void)timer;
  (void)data;

  sl_bt_external_signal(OLED_TIMER_EXT);
}

/**************************************************************************//**
 * @brief   A callback to handle incoming IRQ signal
 *
 * @param[in] pin  The pin used to identify the IRQ that has been set.
 *****************************************************************************/
static void app_hrm_gpio_callback(uint8_t pin)
{
  (void)pin;

  if (!GPIO_PinInGet(MAX86161_GPIO_INT_PORT, MAX86161_GPIO_INT_PIN)) {
    sl_bt_external_signal(MAXM86161_IRQ_EVENT);
  }
}
